package atag.compiler;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import atag.common.ATaGDataItemDeclaration;
import atag.common.ATaGProgram;
import atag.common.ATaGTaskDeclaration;
import atag.common.ScopedChannel;



public class ATaGManagerGenerator {

	/**
	 * Creates the ATaGManager java file
	 *
	 * @param aProgram :
	 *            The ATaG Program Object
	 * @returns false if there is a problem. Otherwise returns true
	 */
	public static boolean CreateATaGManager(ATaGProgram aProgram, String dartRoot) {
		String offset = "\t\t";
		String code = "";
		
		String header = getCommonHeader(aProgram, dartRoot);
		
		String j2se_header = getJ2SEHeader(aProgram, dartRoot);

		code+=""+
		"	public Neighborhood[] getLNScopeForData(int taskID, int dataID) {\n"+
		"\n"+
		"		switch (dataID) {\n"+
		"\n";

		for(ATaGDataItemDeclaration d:aProgram.getDataList()){
			code+=""+
			"			case IDConstants.D_"+d.getName().toUpperCase()+":{\n";
			String newCode="";
			boolean throwingException=false;

			ATaGTaskDeclaration st = aProgram.getProducerOfData(d.getID());
			int channelcount = 0; /* used to generate unique variable names*/
			for(ATaGTaskDeclaration dt: aProgram.getOutTasksListOfData(d.getID())){
				newCode+=""+
				"				// create a holder for the Scopes\n"+
				"				Predicate[] tempPred"+channelcount+" = new Predicate[3];\n"+
				"				int count"+channelcount+" = 0;\n\n";

				/* Create LNPredicate for "node has consumer task instantiated"  */
				newCode+=""+
				"				tempPred"+channelcount+"[count"+channelcount+"++] = new StringSetMembershipPredicate(\n"+
				"						String.valueOf(IDConstants.T_"+dt.getName().toUpperCase()+"),\n"+
				"						StringSetMembershipPredicate.IS_IN,\n"+
				"						NodeInfo.ASSIGNED_TASK_ATTR_NAME);\n\n";

				/* Look at channel between st and d
                  If it is local, do nothing.
                 If it is domain, create a proper predicate
                 If it is region-based, create another proper predicate*/
				//get outChannel
				ScopedChannel aoc = aProgram.getChannelFromTaskAndData(st.getID(),d.getID());
				if(aoc.isRequestResponse())
				{
					String interest = aoc.getInterest();

					if(interest.equalsIgnoreCase("domain")){
						//this is domain.. get region label of associated task and
						//create corresponding predicate
						newCode+=""+
						"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerSimplePredicate(\""+dt.getMyRegionName()+"\",\n"+
						"						IntegerSimplePredicate.EQUAL,\n"+
						"						((Integer) m_config.getAttributeByName(\""+dt.getMyRegionName()+"\")).intValue());\n";
					}else if (interest.equalsIgnoreCase("region-hops")){
						//this is region-hops. make a range predicate
						newCode+=""+
						"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerRangePredicate(\""+dt.getMyRegionName()+"\",\n"+
						"						((Integer) m_config.getAttributeByName(\""+dt.getMyRegionName()+"\")).intValue() - "+Integer.parseInt(aoc.getInterestParam())+",\n"+
						"						((Integer) m_config.getAttributeByName(\""+dt.getMyRegionName()+"\")).intValue() + "+Integer.parseInt(aoc.getInterestParam())+");\n";
					}else if (interest.equalsIgnoreCase("next-region")){
						//this is next-region...adds a predicate for (myregion + 1)
						newCode+=""+
						"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerSimplePredicate(\""+dt.getMyRegionName()+"\",\n"+
						"						IntegerSimplePredicate.EQUAL,\n"+
						"						((Integer) m_config.getAttributeByName(\""+dt.getMyRegionName()+"\")).intValue()+1);\n";
					}else if (interest.equalsIgnoreCase("custom")){
						throwingException=true;
					}
				}
				else 
				{
					String aocInterest = aoc.getInterest();

					if(aocInterest.equalsIgnoreCase("domain")){
						//this is domain.. get region label of associated task and
						//create corresponding predicate
						newCode+=""+
						"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerSimplePredicate(\""+st.getMyRegionName()+"\",\n"+
						"						IntegerSimplePredicate.EQUAL,\n"+
						"						((Integer) m_config.getAttributeByName(\""+st.getMyRegionName()+"\")).intValue());\n\n";
					}else if (aocInterest.equalsIgnoreCase("region-hops")){
						//this is region-hops. make a range predicate
						newCode+=""+
						"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerRangePredicate(\""+st.getMyRegionName()+"\",\n"+
						"						((Integer) m_config.getAttributeByName(\""+st.getMyRegionName()+"\")).intValue() - "+Integer.parseInt(aoc.getInterestParam())+",\n"+
						"						((Integer) m_config.getAttributeByName(\""+st.getMyRegionName()+"\")).intValue() + "+Integer.parseInt(aoc.getInterestParam())+");\n\n";
					}else if (aocInterest.equalsIgnoreCase("custom")){
						throwingException=true;
					}

					/* Look at channel between dt and d
                    If it is local, do nothing.
                    If it is domain, create a proper predicate
                    If it is region-based, create another proper predicate*/
					//get inChannel
					ScopedChannel aic = aProgram.getChannelFromTaskAndData(dt.getID(),d.getID());
					String aicInterest = aic.getInterest();

					if(aicInterest.equalsIgnoreCase("domain")){
						//this is domain.. get region label of associated task and
						//create corresponding predicate
						newCode+=""+
						"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerSimplePredicate(\""+dt.getMyRegionName()+"\",\n"+
						"						IntegerSimplePredicate.EQUAL,\n"+
						"						((Integer) m_config.getAttributeByName(\""+dt.getMyRegionName()+"\")).intValue());\n";
					}else if (aicInterest.equalsIgnoreCase("region-hops")){
						//this is region-hops. make a range predicate
						newCode+=""+
						"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerRangePredicate(\""+dt.getMyRegionName()+"\",\n"+
						"						((Integer) m_config.getAttributeByName(\""+dt.getMyRegionName()+"\")).intValue() - "+Integer.parseInt(aic.getInterestParam())+",\n"+
						"						((Integer) m_config.getAttributeByName(\""+dt.getMyRegionName()+"\")).intValue() + "+Integer.parseInt(aic.getInterestParam())+");\n";
					}else if (aicInterest.equalsIgnoreCase("next-region")){
						//this is next-region...adds a predicate for (myregion + 1)
						newCode+=""+
						"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerSimplePredicate(\""+dt.getMyRegionName()+"\",\n"+
						"						IntegerSimplePredicate.EQUAL,\n"+
						"						((Integer) m_config.getAttributeByName(\""+dt.getMyRegionName()+"\")).intValue()+1);\n";
					}else if (aicInterest.equalsIgnoreCase("custom")){
						throwingException=true;
					}
				}

				/*now combine the predicates*/

				newCode += ""+
				"				Predicate[] finalPred"+channelcount+" = new Predicate[count"+channelcount+"];\n"+
				"				for(int i = 0; i < count"+channelcount+"; i++){\n"+
				"					finalPred"+channelcount+"[i] = tempPred"+channelcount+"[i];\n"+
				"				}\n"+
				"				Neighborhood scopeOfChannel"+channelcount+" = new ConjunctiveNeighborhood(finalPred"+channelcount+");\n"+
				"\n\n";

				channelcount++;/*get ready for another set of variables*/
			}
			newCode+=""+
			"				return new Neighborhood[] { \n";

			newCode+=""+
			"					scopeOfChannel0\n"; /*for the comma*/

			for(int i=1; i<channelcount; i++)
				newCode+=""+
				"				, scopeOfChannel"+i+"\n";

			newCode+=""+
			"					};\n"+
			"\n";
			if(throwingException){
				code+="				throw new IllegalStateException(\"Need to supply partition info with custom scopes\");\n";

			}else
			{
				code+=newCode;

			}
			code+="			}\n";
		}
		code+=""+
		"			default:\n"+
		"				return new Neighborhood[] {};\n"+
		"		}/*end switch*/\n"+
		"	}\n";

		code+=""+
		"	public Neighborhood[] getLNScopeForData(int taskID, int dataID,String partitionLabel,int partitionValue) {\n"+
		"\n"+
		"		switch (dataID) {\n"+
		"\n";

		for(ATaGDataItemDeclaration d:aProgram.getDataList()){
			code+=""+
			"			case IDConstants.D_"+d.getName().toUpperCase()+":{\n";
			String newCode="";
			boolean throwingException=false;

			ATaGTaskDeclaration st = aProgram.getProducerOfData(d.getID());
			int channelcount = 0; /* used to generate unique variable names*/
			for(ATaGTaskDeclaration dt: aProgram.getOutTasksListOfData(d.getID())){
				newCode+=""+
				"				// create a holder for the Scopes\n"+
				"				Predicate[] tempPred"+channelcount+" = new Predicate[3];\n"+
				"				int count"+channelcount+" = 0;\n\n";

				/* Create LNPredicate for "node has consumer task instantiated"  */
				newCode+=""+
				"				tempPred"+channelcount+"[count"+channelcount+"++] = new StringSetMembershipPredicate(\n"+
				"						String.valueOf(IDConstants.T_"+dt.getName().toUpperCase()+"),\n"+
				"						StringSetMembershipPredicate.IS_IN,\n"+
				"						NodeInfo.ASSIGNED_TASK_ATTR_NAME);\n\n";

				ScopedChannel aoc = aProgram.getChannelFromTaskAndData(st.getID(),d.getID());
				String aocInterest = aoc.getInterest();
				ScopedChannel aic = aProgram.getChannelFromTaskAndData(dt.getID(),d.getID());
				String aicInterest = aic.getInterest();

				if(aocInterest.equalsIgnoreCase("custom") || aicInterest.equalsIgnoreCase("custom")){
					newCode+=""+
					"				tempPred"+channelcount+"[count"+channelcount+"++] = new IntegerSimplePredicate(\n"+
					"						partitionLabel"+",\n"+
					"						IntegerSimplePredicate.EQUAL,\n"+
					"						partitionValue);\n\n";
				}else{
					throwingException=true;
				}


				/*now combine the predicates*/

				newCode += ""+
				"				Predicate[] finalPred"+channelcount+" = new Predicate[count"+channelcount+"];\n"+
				"				for(int i = 0; i < count"+channelcount+"; i++){\n"+
				"					finalPred"+channelcount+"[i] = tempPred"+channelcount+"[i];\n"+
				"				}\n"+
				"				Neighborhood scopeOfChannel"+channelcount+" = new ConjunctiveNeighborhood(finalPred"+channelcount+");\n"+
				"\n\n";

				channelcount++;/*get ready for another set of variables*/
			}
			newCode+=""+
			"				return new Neighborhood[] { \n";

			newCode+=""+
			"					scopeOfChannel0\n"; /*for the comma*/

			for(int i=1; i<channelcount; i++)
				newCode+=""+
				"				, scopeOfChannel"+i+"\n";

			newCode+=""+
			"					};\n"+
			"\n";
			if(throwingException){
				code+="				throw new IllegalStateException(\"Can supply partition info only with custom scopes\");\n";

			}else
			{
				code+=newCode;

			}
			code+="			}\n";
		}
		code+=""+
		"			default:\n"+
		"				return new Neighborhood[] {};\n"+
		"		}/*end switch*/\n"+
		"	}\n";

		code+=   "}";



		try {
			writeStringtoFile(
					dartRoot + File.separator+"app-template-freespot"
					+File.separator + "src" +
					File.separator + "atag" + File.separator + "apps" +
					File.separator + "mainApp" +
					File.separator + "gen" +
					File.separator+ "PreBuiltAtagManager.java",
					header + code, true);

			writeStringtoFile(
					dartRoot + File.separator+"app-template-hostspot"+
					File.separator + "src" +
					File.separator + "atag" + File.separator + "apps" +
					File.separator + "mainApp" +
					File.separator + "gen" +
					File.separator+ "PreBuiltAtagManager.java",
					header + code, true);

			writeStringtoFile(
					dartRoot + File.separator+"app-template-j2se"+
					File.separator + "src" +
					File.separator + "main" +
					File.separator + "java" +
					File.separator + "atag" + File.separator + "apps" +
					File.separator + "mainApp" +
					File.separator + "gen" +
					File.separator+ "PreBuiltAtagManager.java",
					j2se_header + code, true);

			writeStringtoFile(
					dartRoot + File.separator+"app-template-android"+
					File.separator + "src" +
					File.separator + "atag" + File.separator + "apps" +
					File.separator + "mainApp" +
					File.separator + "gen" +
					File.separator+ "PreBuiltAtagManager.java",
					header + code, true);

		} catch (IOException ex) {
			System.err.println("Could not write " +
					File.separator + "src" +
					File.separator + "atag" + File.separator + "apps" +
					File.separator + "mainApp" +
					File.separator + "gen" +
					File.separator+ "PreBuiltAtagManager.java");

			System.err.println(ex.getMessage());
			return false;
		}
		return true;
	}
	
	private static String getCommonHeader(ATaGProgram aProgram, String dartRoot) {
		String code = ""+
		"/*********************/\n"+
		"/** PreBuiltAtagManager.java */\n"+
		"/*********************/\n"+
		"package atag.apps.mainApp.gen;\n"+
		"\n"+
		"import atag.common.*;\n"+
		"import atag.runtime.*;\n"+
		"import atag.runtime.ln.*;\n"+
		"import atag.runtime.ln.neighborhoodDefs.*;\n"+
		"import atag.apps.mainApp.tasks.*;\n\n"+
		"public class PreBuiltAtagManager extends AtagManager {\n"+
		"\n"+
		"	public PreBuiltAtagManager() {\n"+
		"		\n"+
		"	}\n"+
		"\n"+
		"	public void setUp() {\n";
		
		for(ATaGTaskDeclaration t : aProgram.getTaskList()){

			// Handle External Tasks
			if(t.getFiringRule()[0] == ATaGTaskDeclaration.FIRING_EXTERN)
			{
				continue;
			}

			code+=""+
			"		taskDecls.addElement(new RunnableTask((ATaGTaskDeclaration) this.m_program.getTaskList().elementAt("+t.getID()+"),new "+t.getName()+"(this.m_dataPool, this.m_config),\n"+
			"				Thread.MAX_PRIORITY - "+t.getPriority()+"));\n";
		}

		code+=""+
		"	}\n"+
		"\n";
		return code;
	}
	
	private static String getJ2SEHeader(ATaGProgram aProgram, String dartRoot){
		if (!isFTnode(aProgram)){
			return getCommonHeader(aProgram, dartRoot);
		}
		String code = ""+
		"/*********************/\n"+
		"/** PreBuiltAtagManager.java */\n"+
		"/*********************/\n"+
		"package atag.apps.mainApp.gen;\n"+
		"\n"+
		"import atag.common.*;\n"+
		"import atag.runtime.*;\n"+
		"import atag.runtime.ln.*;\n"+
		"import atag.runtime.ln.neighborhoodDefs.*;\n"+
		"import atag.apps.mainApp.tasks.*;\n\n" +
		"import org.objectweb.fractal.api.Component;\n" +
		"import org.ow2.frascati.FraSCAti;\n" +
		"import org.ow2.frascati.util.FrascatiException;\n\n" +
		"import fr.inria.arles.srijan.ftm.api.FTMProtocol;\n" +
		"import fr.inria.arles.srijan.ftm.api.FTTask;\n" +
		"import fr.inria.arles.srijan.ftm.api.FTTaskController;\n" +
		"import fr.inria.arles.srijan.ftm.api.TaskStarter;\n" +
		"import fr.inria.arles.srijan.ftm.api.TriggerTaskStarter;\n\n"+
		"public class PreBuiltAtagManager extends AtagManager {\n"+
		"\n" +
		"	private FraSCAti frascati;\n" +
		"	private Component composite;\n" +
		"\n"+
		"	public PreBuiltAtagManager() {\n"+
		"		\n"+
		"	}\n"+
		"\n";
		
		code += "	public String startTask(Integer id) {\n" +
				"		return this.m_config.assignTask(id.intValue());\n" +
				"	}\n\n";
	
		code += "	public String stopTask(Integer id) {\n" +
				"		return this.m_config.removeTask(id.intValue());\n" +
				"	}\n\n";
	
		code += "	private FTMProtocol getFTMProtocol(Integer ftNodeType) {\n" +
				"		FTMProtocol ft = null;\n" +
				"		TriggerTaskStarter ts = null;\n" +
				"\n" +
				"		try {\n" +
				"			System.out.println(\"------------------ >>> Loading FraSCAti\");\n" +
				"			frascati = org.ow2.frascati.FraSCAti.newFraSCAti();\n" +
				"\n			" +
				"			System.out.println(\"------------------ >>> Getting composite\");\n" +
				"			composite = frascati.getComposite(\"srijan-node.composite\");\n" +
				"\n" +
				"			if (ftNodeType == 1) { // master\n" +
				"				System.out.println(\"------------------ >>> Getting FT Protocol\");\n" +
				"				ft = frascati.getService(composite, \"r\", FTMProtocol.class);\n" +
				"			} else if (ftNodeType == 2){ // slave\n" +
				"				System.out.println(\"------------------ >>> Getting TaskStarterService\");\n" +
				"				ts = frascati.getService(composite, \"taskStarter\", TriggerTaskStarter.class);\n" +
				"				ts.setTaskStarter(this);\n" +
				"			}\n" +
				"\n" +
				"			System.out.println(\"------------------ >>> ft = \" + ft);\n" +
				"		} catch (FrascatiException e) {\n" +
				"			e.printStackTrace();\n" +
				"		}\n" +
				"\n" +
				"	return ft;\n" +
				"}\n\n";	
	
		code += "	private void setFTTask(FTTask ft){\n" +
				"		try {\n" +
				"			FTTaskController ftc;\n" +
				"			ftc = frascati.getService(composite, \"taskController\", FTTaskController.class);\n" +
				"			ftc.setFTTask(ft);\n" +
				"		} catch (FrascatiException e) {\n" +
				"			e.printStackTrace();\n" +
				"		}\n" +
				"	}\n\n";
		
		code += "	public void setUp() {\n";
		
		for(ATaGTaskDeclaration t : aProgram.getTaskList()){

			// Handle External Tasks
			if(t.getFiringRule()[0] == ATaGTaskDeclaration.FIRING_EXTERN)
			{
				continue;
			}
			
			if (t.getFaultTolerance() == ATaGTaskDeclaration.FAULTTOLERANCE_REPLICATION){
				code+=""+
				"		Integer ftNodeType = (Integer) m_config.getAttributeByName(\"ft-node-type\");" +
				"		"+t.getName()+" fttask = null;\n" +
				"		if (ftNodeType != 0){\n" +
				"			fttask = new LightCollector(this.m_dataPool, this.m_config, getFTMProtocol(ftNodeType));\n" +
				"			setFTTask(fttask);\n" +
				"		} else {\n" +
				"			fttask = new "+t.getName()+"(this.m_dataPool, this.m_config, null);\n" +
				"		}\n" +
				"		taskDecls.addElement(new RunnableTask(\n" +
				"				(ATaGTaskDeclaration) this.m_program.getTaskList().elementAt("+t.getID()+"),\n" +
				"				fttask,\n"+
				"				Thread.MAX_PRIORITY - "+t.getPriority()+"));\n";
			} else {
				code+=""+
				"		taskDecls.addElement(new RunnableTask(\n" +
				"				(ATaGTaskDeclaration) this.m_program.getTaskList().elementAt("+t.getID()+"),\n" +
				"				new "+t.getName()+"(this.m_dataPool, this.m_config),\n"+
				"				Thread.MAX_PRIORITY - "+t.getPriority()+"));\n";
			}
		}

		code+=""+
		"	}\n"+
		"\n";
		return code;
		
	}

	private static boolean isFTnode(ATaGProgram aProgram){
		for(ATaGTaskDeclaration t : aProgram.getTaskList()){
			if (t.getFaultTolerance() == ATaGTaskDeclaration.FAULTTOLERANCE_REPLICATION){
				return true;
			}
		}
		return false;
	}

	private synchronized static void writeStringtoFile(String path, String src, boolean overwrite)
	throws IOException {

		File file = new File(path);
		if (file.exists()) {
			if (overwrite) {
				System.out.println("OVERWRITING existing file at path:");
				System.out.println("\t"+path);
				System.out.println();                
			} else {
				System.out.println("RETAINING existing file at path:");
				System.out.println("\t"+path);
				System.out.println(); 
				return;
			}
		}        
		BufferedWriter writer = new BufferedWriter(new FileWriter(path));
		writer.write(src);
		writer.close();
	}

}